package com.erp.framework.util.ftp;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Properties;
import java.util.Vector;

public class FTPClient {

    /**
     * Revision control id
     */
    private static String cvsId = "@(#)$Id: FTPClient.java,v 1.1 2006/04/12 08:25:21 ric.yik Exp $";

    /**
     * Format to interpret MTDM timestamp
     */
    private SimpleDateFormat tsFormat = new SimpleDateFormat("yyyyMMddHHmmss");

    /**
     * Socket responsible for controlling the connection
     */
    private FTPControlSocket control = null;

    /**
     * Socket responsible for transferring the data
     */
    private FTPDataSocket data = null;

    /**
     * Socket timeout for both data and control. In milliseconds
     */
    private int timeout = 0;

    /**
     * Record of the transfer type - make the default BINARY
     */
    private FTPTransferType transferType = FTPTransferType.BINARY;

    /**
     * Record of the connect mode - make the default PASV (as this was the
     * original mode supported)
     */
    private FTPConnectMode connectMode = FTPConnectMode.PASV;

    /**
     * Holds the last valid reply from the server on the control socket
     */
    private FTPReply lastValidReply;

    /**
     * Constructor. Creates the control socket
     * 
     * @param remoteHost
     *            the remote hostname
     */
    public FTPClient(String remoteHost) throws IOException, FTPException {

        control = new FTPControlSocket(remoteHost, FTPControlSocket.CONTROL_PORT, null, 0);
    }

    /**
     * Constructor. Creates the control socket
     * 
     * @param remoteHost
     *            the remote hostname
     * @param controlPort
     *            port for control stream
     */
    public FTPClient(String remoteHost, int controlPort) throws IOException, FTPException {

        control = new FTPControlSocket(remoteHost, controlPort, null, 0);
    }

    /**
     * Constructor. Creates the control socket
     * 
     * @param remoteAddr
     *            the address of the remote host
     */
    public FTPClient(InetAddress remoteAddr) throws IOException, FTPException {

        control = new FTPControlSocket(remoteAddr, FTPControlSocket.CONTROL_PORT, null, 0);
    }

    /**
     * Constructor. Creates the control socket. Allows setting of control port
     * (normally set by default to 21).
     * 
     * @param remoteAddr
     *            the address of the remote host
     * @param controlPort
     *            port for control stream
     */
    public FTPClient(InetAddress remoteAddr, int controlPort) throws IOException, FTPException {

        control = new FTPControlSocket(remoteAddr, controlPort, null, 0);
    }

    /**
     * Constructor. Creates the control socket
     * 
     * @param remoteHost
     *            the remote hostname
     * @param millis
     *            the length of the timeout, in milliseconds
     */
    public FTPClient(String remoteHost, PrintWriter log, int timeout) throws IOException, FTPException {

        control = new FTPControlSocket(remoteHost, FTPControlSocket.CONTROL_PORT, log, timeout);
    }

    /**
     * Constructor. Creates the control socket
     * 
     * @param remoteHost
     *            the remote hostname
     * @param controlPort
     *            port for control stream
     * @param millis
     *            the length of the timeout, in milliseconds
     */
    public FTPClient(String remoteHost, int controlPort, PrintWriter log, int timeout) throws IOException, FTPException {

        control = new FTPControlSocket(remoteHost, controlPort, log, timeout);
    }

    /**
     * Constructor. Creates the control socket
     * 
     * @param remoteAddr
     *            the address of the remote host
     * @param millis
     *            the length of the timeout, in milliseconds
     */
    public FTPClient(InetAddress remoteAddr, PrintWriter log, int timeout) throws IOException, FTPException {

        control = new FTPControlSocket(remoteAddr, FTPControlSocket.CONTROL_PORT, log, timeout);
    }

    /**
     * Constructor. Creates the control socket. Allows setting of control port
     * (normally set by default to 21).
     * 
     * @param remoteAddr
     *            the address of the remote host
     * @param controlPort
     *            port for control stream
     * @param millis
     *            the length of the timeout, in milliseconds
     */
    public FTPClient(InetAddress remoteAddr, int controlPort, PrintWriter log, int timeout) throws IOException, FTPException {

        control = new FTPControlSocket(remoteAddr, controlPort, log, timeout);
    }

    /**
     * Set the TCP timeout on the underlying socket.
     * 
     * If a timeout is set, then any operation which takes longer than the
     * timeout value will be killed with a java.io.InterruptedException. We set
     * both the control and data connections
     * 
     * @param millis
     *            The length of the timeout, in milliseconds
     */
    public void setTimeout(int millis) throws IOException {

        this.timeout = millis;
        control.setTimeout(millis);
    }

    /**
     * Set the connect mode
     * 
     * @param mode
     *            ACTIVE or PASV mode
     */
    public void setConnectMode(FTPConnectMode mode) {
        connectMode = mode;
    }

    /**
     * Login into an account on the FTP server. This call completes the entire
     * login process
     * 
     * @param user
     *            user name
     * @param password
     *            user's password
     */
    public void login(String user, String password) throws IOException, FTPException {

        String response = control.sendCommand("USER " + user);
        lastValidReply = control.validateReply(response, "331");
        response = control.sendCommand("PASS " + password);
        lastValidReply = control.validateReply(response, "230");
    }

    /**
     * Supply the user name to log into an account on the FTP server. Must be
     * followed by the password() method - but we allow for
     * 
     * @param user
     *            user name
     * @param password
     *            user's password
     */
    public void user(String user) throws IOException, FTPException {

        String reply = control.sendCommand("USER " + user);

        // we allow for a site with no password - 230 response
        String[] validCodes = { "230", "331" };
        lastValidReply = control.validateReply(reply, validCodes);
    }

    /**
     * Supplies the password for a previously supplied username to log into the
     * FTP server. Must be preceeded by the user() method
     * 
     * @param user
     *            user name
     * @param password
     *            user's password
     */
    public void password(String password) throws IOException, FTPException {

        String reply = control.sendCommand("PASS " + password);

        // we allow for a site with no passwords (202)
        String[] validCodes = { "230", "202" };
        lastValidReply = control.validateReply(reply, validCodes);
    }

    /**
     * Set up SOCKS v4/v5 proxy settings. This can be used if there is a SOCKS
     * proxy server in place that must be connected thru. Note that setting
     * these properties directs <b>all</b> TCP sockets in this JVM to the SOCKS
     * proxy
     * 
     * @param port
     *            SOCKS proxy port
     * @param host
     *            SOCKS proxy hostname
     */
    public static void initSOCKS(String port, String host) {
        Properties props = System.getProperties();
        props.put("socksProxyPort", port);
        props.put("socksProxyHost", host);
        System.setProperties(props);
    }

    /**
     * Set up SOCKS username and password for SOCKS username/password
     * authentication. Often, no authentication will be required but the SOCKS
     * server may be configured to request these.
     * 
     * @param username
     *            the SOCKS username
     * @param password
     *            the SOCKS password
     */
    public static void initSOCKSAuthentication(String username, String password) {
        Properties props = System.getProperties();
        props.put("java.net.socks.username", username);
        props.put("java.net.socks.password", password);
        System.setProperties(props);
    }

    /**
     * Get the name of the remote host
     * 
     * @return remote host name
     */
    String getRemoteHostName() {
        return control.getRemoteHostName();
    }

    /**
     * Issue arbitrary ftp commands to the FTP server.
     * 
     * @param command
     *            ftp command to be sent to server
     * @param validCodes
     *            valid return codes for this command
     */
    public void quote(String command, String[] validCodes) throws IOException, FTPException {

        String reply = control.sendCommand(command);

        // allow for no validation to be supplied
        if (validCodes != null && validCodes.length > 0)
            lastValidReply = control.validateReply(reply, validCodes);
    }

    /**
     * Put a local file onto the FTP server. It is placed in the current
     * directory.
     * 
     * @param localPath
     *            path of the local file
     * @param remoteFile
     *            name of remote file in current directory
     */
    public void put(String localPath, String remoteFile) throws IOException, FTPException {

        put(localPath, remoteFile, false);
    }

    /**
     * Put a stream of data onto the FTP server. It is placed in the current
     * directory.
     * 
     * @param srcStream
     *            input stream of data to put
     * @param remoteFile
     *            name of remote file in current directory
     */
    public void put(InputStream srcStream, String remoteFile) throws IOException, FTPException {

        put(srcStream, remoteFile, false);
    }

    /**
     * Put a local file onto the FTP server. It is placed in the current
     * directory. Allows appending if current file exists
     * 
     * @param localPath
     *            path of the local file
     * @param remoteFile
     *            name of remote file in current directory
     * @param append
     *            true if appending, false otherwise
     */
    public void put(String localPath, String remoteFile, boolean append) throws IOException, FTPException {

        // get according to set type
        if (getType() == FTPTransferType.ASCII) {
            putASCII(localPath, remoteFile, append);
        } else {
            putBinary(localPath, remoteFile, append);
        }
        validateTransfer();
    }

    /**
     * Put a stream of data onto the FTP server. It is placed in the current
     * directory. Allows appending if current file exists
     * 
     * @param srcStream
     *            input stream of data to put
     * @param remoteFile
     *            name of remote file in current directory
     * @param append
     *            true if appending, false otherwise
     */
    public void put(InputStream srcStream, String remoteFile, boolean append) throws IOException, FTPException {

        // get according to set type
        if (getType() == FTPTransferType.ASCII) {
            putASCII(srcStream, remoteFile, append);
        } else {
            putBinary(srcStream, remoteFile, append);
        }
        validateTransfer();
    }

    /**
     * Validate that the put() or get() was successful
     */
    private void validateTransfer() throws IOException, FTPException {

        // check the control response
        String[] validCodes = { "226", "250" };
        String reply = control.readReply();
        lastValidReply = control.validateReply(reply, validCodes);
    }

    /**
     * Request the server to set up the put
     * 
     * @param remoteFile
     *            name of remote file in current directory
     * @param append
     *            true if appending, false otherwise
     */
    private void initPut(String remoteFile, boolean append) throws IOException, FTPException {

        // set up data channel
        data = control.createDataSocket(connectMode);
        data.setTimeout(timeout);

        // send the command to store
        String cmd = append ? "APPE " : "STOR ";
        String reply = control.sendCommand(cmd + remoteFile);

        // Can get a 125 or a 150
        String[] validCodes = { "125", "150" };
        lastValidReply = control.validateReply(reply, validCodes);
    }

    /**
     * Put as ASCII, i.e. read a line at a time and write inserting the correct
     * FTP separator
     * 
     * @param localPath
     *            full path of local file to read from
     * @param remoteFile
     *            name of remote file we are writing to
     * @param append
     *            true if appending, false otherwise
     */
    private void putASCII(String localPath, String remoteFile, boolean append) throws IOException, FTPException {

        // create an inputstream & pass to common method
        InputStream srcStream = new FileInputStream(localPath);
        putASCII(srcStream, remoteFile, append);
    }

    /**
     * Put as ASCII, i.e. read a line at a time and write inserting the correct
     * FTP separator
     * 
     * @param srcStream
     *            input stream of data to put
     * @param remoteFile
     *            name of remote file we are writing to
     * @param append
     *            true if appending, false otherwise
     */
    private void putASCII(InputStream srcStream, String remoteFile, boolean append) throws IOException, FTPException {

        // need to read line by line ...
        LineNumberReader in = new LineNumberReader(new InputStreamReader(srcStream));

        initPut(remoteFile, append);

        // get an character output stream to write to ... AFTER we
        // have the ok to go ahead AND AFTER we've successfully opened a
        // stream for the local file
        BufferedWriter out = new BufferedWriter(new OutputStreamWriter(data.getOutputStream()));

        // write line by line, writing \r\n as required by RFC959 after
        // each line
        String line = null;
        while ((line = in.readLine()) != null) {
            out.write(line, 0, line.length());
            out.write(FTPControlSocket.EOL, 0, FTPControlSocket.EOL.length());
        }
        in.close();
        out.flush();
        out.close();

        // and close the data socket
        try {
            data.close();
        } catch (IOException ignore) {
        }
    }

    /**
     * Put as binary, i.e. read and write raw bytes
     * 
     * @param localPath
     *            full path of local file to read from
     * @param remoteFile
     *            name of remote file we are writing to
     * @param append
     *            true if appending, false otherwise
     */
    private void putBinary(String localPath, String remoteFile, boolean append) throws IOException, FTPException {

        // open input stream to read source file ... do this
        // BEFORE opening output stream to server, so if file not
        // found, an exception is thrown
        InputStream srcStream = new FileInputStream(localPath);
        putBinary(srcStream, remoteFile, append);
    }

    /**
     * Put as binary, i.e. read and write raw bytes
     * 
     * @param srcStream
     *            input stream of data to put
     * @param remoteFile
     *            name of remote file we are writing to
     * @param append
     *            true if appending, false otherwise
     */
    private void putBinary(InputStream srcStream, String remoteFile, boolean append) throws IOException, FTPException {

        BufferedInputStream in = new BufferedInputStream(srcStream);

        initPut(remoteFile, append);

        // get an output stream
        BufferedOutputStream out = new BufferedOutputStream(new DataOutputStream(data.getOutputStream()));

        byte[] buf = new byte[512];

        // read a chunk at a time and write to the data socket
        long size = 0;
        int count = 0;
        while ((count = in.read(buf)) > 0) {
            out.write(buf, 0, count);
            size += count;
        }

        in.close();

        // flush and clean up
        out.flush();
        out.close();

        // and close the data socket
        try {
            data.close();
        } catch (IOException ignore) {
        }

        // log bytes transferred
        control.log("Transferred " + size + " bytes to remote host");
    }

    /**
     * Put data onto the FTP server. It is placed in the current directory.
     * 
     * @param data
     *            array of bytes
     * @param remoteFile
     *            name of remote file in current directory
     */
    public void put(byte[] bytes, String remoteFile) throws IOException, FTPException {

        put(bytes, remoteFile, false);
    }

    /**
     * Put data onto the FTP server. It is placed in the current directory.
     * Allows appending if current file exists
     * 
     * @param data
     *            array of bytes
     * @param remoteFile
     *            name of remote file in current directory
     * @param append
     *            true if appending, false otherwise
     */
    public void put(byte[] bytes, String remoteFile, boolean append) throws IOException, FTPException {

        initPut(remoteFile, append);

        // get an output stream
        BufferedOutputStream out = new BufferedOutputStream(new DataOutputStream(data.getOutputStream()));

        // write array
        out.write(bytes, 0, bytes.length);

        // flush and clean up
        out.flush();
        out.close();

        // and close the data socket
        try {
            data.close();
        } catch (IOException ignore) {
        }

        validateTransfer();
    }

    /**
     * Get data from the FTP server. Uses the currently set transfer mode.
     * 
     * @param localPath
     *            local file to put data in
     * @param remoteFile
     *            name of remote file in current directory
     */
    public void get(String localPath, String remoteFile) throws IOException, FTPException {

        // get according to set type
        if (getType() == FTPTransferType.ASCII) {
            getASCII(localPath, remoteFile);
        } else {
            getBinary(localPath, remoteFile);
        }
        validateTransfer();
    }

    /**
     * Get data from the FTP server. Uses the currently set transfer mode.
     * 
     * @param destStream
     *            data stream to write data to
     * @param remoteFile
     *            name of remote file in current directory
     */
    public void get(OutputStream destStream, String remoteFile) throws IOException, FTPException {

        // get according to set type
        if (getType() == FTPTransferType.ASCII) {
            getASCII(destStream, remoteFile);
        } else {
            getBinary(destStream, remoteFile);
        }
        validateTransfer();
    }

    /**
     * Request to the server that the get is set up
     * 
     * @param remoteFile
     *            name of remote file
     */
    private void initGet(String remoteFile) throws IOException, FTPException {

        // set up data channel
        data = control.createDataSocket(connectMode);
        data.setTimeout(timeout);

        // send the retrieve command
        String reply = control.sendCommand("RETR " + remoteFile);

        // Can get a 125 or a 150
        String[] validCodes1 = { "125", "150" };
        lastValidReply = control.validateReply(reply, validCodes1);
    }

    /**
     * Get as ASCII, i.e. read a line at a time and write using the correct
     * newline separator for the OS
     * 
     * @param localPath
     *            full path of local file to write to
     * @param remoteFile
     *            name of remote file
     */
    private void getASCII(String localPath, String remoteFile) throws IOException, FTPException {

        // B.McKeown:
        // Call initGet() before creating the FileOutputStream.
        // This will prevent being left with an empty file if a FTPException
        // is thrown by initGet().
        initGet(remoteFile);

        // B. McKeown: Need to store the local file name so the file can be
        // deleted if necessary.
        File localFile = new File(localPath);

        // create the buffered stream for writing
        BufferedWriter out = new BufferedWriter(new FileWriter(localPath));

        // get an character input stream to read data from ... AFTER we
        // have the ok to go ahead AND AFTER we've successfully opened a
        // stream for the local file
        LineNumberReader in = new LineNumberReader(new InputStreamReader(data.getInputStream()));

        // B. McKeown:
        // If we are in active mode we have to set the timeout of the passive
        // socket. We can achieve this by calling setTimeout() again.
        // If we are in passive mode then we are merely setting the value twice
        // which does no harm anyway. Doing this simplifies any logic changes.
        data.setTimeout(timeout);

        // read/write a line at a time
        IOException storedEx = null;
        String line = null;
        try {
            while ((line = in.readLine()) != null) {
                out.write(line, 0, line.length());
                out.newLine();
            }
        } catch (IOException ex) {
            storedEx = ex;
            localFile.delete();
        } finally {
            out.close();
        }

        try {
            in.close();
            data.close();
        } catch (IOException ignore) {
        }

        // if we failed to write the file, rethrow the exception
        if (storedEx != null)
            throw storedEx;
    }

    /**
     * Get as ASCII, i.e. read a line at a time and write using the correct
     * newline separator for the OS
     * 
     * @param destStream
     *            data stream to write data to
     * @param remoteFile
     *            name of remote file
     */
    private void getASCII(OutputStream destStream, String remoteFile) throws IOException, FTPException {

        initGet(remoteFile);

        // create the buffered stream for writing
        BufferedWriter out = new BufferedWriter(new OutputStreamWriter(destStream));

        // get an character input stream to read data from ... AFTER we
        // have the ok to go ahead
        LineNumberReader in = new LineNumberReader(new InputStreamReader(data.getInputStream()));

        // B. McKeown:
        // If we are in active mode we have to set the timeout of the passive
        // socket. We can achieve this by calling setTimeout() again.
        // If we are in passive mode then we are merely setting the value twice
        // which does no harm anyway. Doing this simplifies any logic changes.
        data.setTimeout(timeout);

        // read/write a line at a time
        IOException storedEx = null;
        String line = null;
        try {
            while ((line = in.readLine()) != null) {
                out.write(line, 0, line.length());
                out.newLine();
            }
        } catch (IOException ex) {
            storedEx = ex;
        } finally {
            out.close();
        }

        try {
            in.close();
            data.close();
        } catch (IOException ignore) {
        }

        // if we failed to write the file, rethrow the exception
        if (storedEx != null)
            throw storedEx;
    }

    /**
     * Get as binary file, i.e. straight transfer of data
     * 
     * @param localPath
     *            full path of local file to write to
     * @param remoteFile
     *            name of remote file
     */
    private void getBinary(String localPath, String remoteFile) throws IOException, FTPException {

        // B.McKeown:
        // Call initGet() before creating the FileOutputStream.
        // This will prevent being left with an empty file if a FTPException
        // is thrown by initGet().
        initGet(remoteFile);

        // B. McKeown: Need to store the local file name so the file can be
        // deleted if necessary.
        File localFile = new File(localPath);

        // create the buffered output stream for writing the file
        BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(localPath, false));

        // get an input stream to read data from ... AFTER we have
        // the ok to go ahead AND AFTER we've successfully opened a
        // stream for the local file
        BufferedInputStream in = new BufferedInputStream(new DataInputStream(data.getInputStream()));

        // B. McKeown:
        // If we are in active mode we have to set the timeout of the passive
        // socket. We can achieve this by calling setTimeout() again.
        // If we are in passive mode then we are merely setting the value twice
        // which does no harm anyway. Doing this simplifies any logic changes.
        data.setTimeout(timeout);

        // do the retrieving
        long size = 0;
        int chunksize = 4096;
        byte[] chunk = new byte[chunksize];
        int count;
        IOException storedEx = null;

        // read from socket & write to file in chunks
        try {
            while ((count = in.read(chunk, 0, chunksize)) >= 0) {
                out.write(chunk, 0, count);
                size += count;
            }
        } catch (IOException ex) {
            storedEx = ex;
            localFile.delete();
        } finally {
            out.close();
        }

        // close streams
        try {
            in.close();
            data.close();
        } catch (IOException ignore) {
        }

        // if we failed to write the file, rethrow the exception
        if (storedEx != null)
            throw storedEx;

        // log bytes transferred
        control.log("Transferred " + size + " bytes from remote host");
    }

    /**
     * Get as binary file, i.e. straight transfer of data
     * 
     * @param destStream
     *            stream to write to
     * @param remoteFile
     *            name of remote file
     */
    private void getBinary(OutputStream destStream, String remoteFile) throws IOException, FTPException {

        initGet(remoteFile);

        // create the buffered output stream for writing the file
        BufferedOutputStream out = new BufferedOutputStream(destStream);

        // get an input stream to read data from ... AFTER we have
        // the ok to go ahead AND AFTER we've successfully opened a
        // stream for the local file
        BufferedInputStream in = new BufferedInputStream(new DataInputStream(data.getInputStream()));

        // B. McKeown:
        // If we are in active mode we have to set the timeout of the passive
        // socket. We can achieve this by calling setTimeout() again.
        // If we are in passive mode then we are merely setting the value twice
        // which does no harm anyway. Doing this simplifies any logic changes.
        data.setTimeout(timeout);

        // do the retrieving
        long size = 0;
        int chunksize = 4096;
        byte[] chunk = new byte[chunksize];
        int count;
        IOException storedEx = null;

        // read from socket & write to file in chunks
        try {
            while ((count = in.read(chunk, 0, chunksize)) >= 0) {
                out.write(chunk, 0, count);
                size += count;
            }
        } catch (IOException ex) {
            storedEx = ex;
        } finally {
            out.close();
        }

        // close streams
        try {
            in.close();
            data.close();
        } catch (IOException ignore) {
        }

        // if we failed to write to the stream, rethrow the exception
        if (storedEx != null)
            throw storedEx;

        // log bytes transferred
        control.log("Transferred " + size + " bytes from remote host");
    }

    /**
     * Get data from the FTP server. Transfers in whatever mode we are in.
     * Retrieve as a byte array. Note that we may experience memory limitations
     * as the entire file must be held in memory at one time.
     * 
     * @param remoteFile
     *            name of remote file in current directory
     */
    public byte[] get(String remoteFile) throws IOException, FTPException {

        initGet(remoteFile);

        // get an input stream to read data from
        BufferedInputStream in = new BufferedInputStream(new DataInputStream(data.getInputStream()));

        // B. McKeown:
        // If we are in active mode we have to set the timeout of the passive
        // socket. We can achieve this by calling setTimeout() again.
        // If we are in passive mode then we are merely setting the value twice
        // which does no harm anyway. Doing this simplifies any logic changes.
        data.setTimeout(timeout);

        // do the retrieving
        int chunksize = 4096;
        byte[] chunk = new byte[chunksize]; // read chunks into
        byte[] resultBuf = null; // where we place result
        ByteArrayOutputStream temp = new ByteArrayOutputStream(chunksize); // temp
        // swap
        // buffer
        int count; // size of chunk read

        // read from socket & write to file
        while ((count = in.read(chunk, 0, chunksize)) >= 0) {
            temp.write(chunk, 0, count);
        }
        temp.close();

        // get the bytes from the temp buffer
        resultBuf = temp.toByteArray();

        // close streams
        try {
            in.close();
            data.close();
        } catch (IOException ignore) {
        }

        validateTransfer();

        return resultBuf;
    }

    /**
     * Run a site-specific command on the server. Support for commands is
     * dependent on the server
     * 
     * @param command
     *            the site command to run
     * @return true if command ok, false if command not implemented
     */
    public boolean site(String command) throws IOException, FTPException {

        // send the retrieve command
        String reply = control.sendCommand("SITE " + command);

        // Can get a 200 (ok) or 202 (not impl). Some
        // FTP servers return 502 (not impl)
        String[] validCodes = { "200", "202", "502" };
        lastValidReply = control.validateReply(reply, validCodes);

        // return true or false? 200 is ok, 202/502 not
        // implemented
        if (reply.substring(0, 3).equals("200"))
            return true;
        else
            return false;
    }

    /**
     * List a directory's contents
     * 
     * @param dirname
     *            the name of the directory (<b>not</b> a file mask)
     * @return a string containing the line separated directory listing
     * @deprecated As of FTP 1.1, replaced by {@link #dir(String)}
     */
    public String list(String dirname) throws IOException, FTPException {

        return list(dirname, false);
    }

    /**
     * List a directory's contents as one string. A detailed listing is
     * available, otherwise just filenames are provided. The detailed listing
     * varies in details depending on OS and FTP server.
     * 
     * @param dirname
     *            the name of the directory(<b>not</b> a file mask)
     * @param full
     *            true if detailed listing required false otherwise
     * @return a string containing the line separated directory listing
     * @deprecated As of FTP 1.1, replaced by {@link #dir(String,boolean)}
     */
    public String list(String dirname, boolean full) throws IOException, FTPException {

        String[] list = dir(dirname, full);

        StringBuffer result = new StringBuffer();
        String sep = System.getProperty("line.separator");

        // loop thru results and make into one string
        for (int i = 0; i < list.length; i++) {
            result.append(list[i]);
            result.append(sep);
        }

        return result.toString();
    }

    /**
     * List current directory's contents as an array of strings of filenames.
     * 
     * @return an array of current directory listing strings
     */
    public String[] dir() throws IOException, FTPException {

        return dir(null, false);
    }

    /**
     * List a directory's contents as an array of strings of filenames.
     * 
     * @param dirname
     *            name of directory(<b>not</b> a file mask)
     * @return an array of directory listing strings
     */
    public String[] dir(String dirname) throws IOException, FTPException {

        return dir(dirname, false);
    }

    /**
     * List a directory's contents as an array of strings. A detailed listing is
     * available, otherwise just filenames are provided. The detailed listing
     * varies in details depending on OS and FTP server. Note that a full
     * listing can be used on a file name to obtain information about a file
     * 
     * @param dirname
     *            name of directory (<b>not</b> a file mask)
     * @param full
     *            true if detailed listing required false otherwise
     * @return an array of directory listing strings
     */
    public String[] dir(String dirname, boolean full) throws IOException, FTPException {

        // set up data channel
        data = control.createDataSocket(connectMode);
        data.setTimeout(timeout);

        // send the retrieve command
        String command = full ? "LIST " : "NLST ";
        if (dirname != null)
            command += dirname;

        // some FTP servers bomb out if NLST has whitespace appended
        command = command.trim();
        String reply = control.sendCommand(command);

        // check the control response. wu-ftp returns 550 if the
        // directory is empty, so we handle 550 appropriately. Similarly
        // proFTPD returns 450
        String[] validCodes1 = { "125", "150", "450", "550" };
        lastValidReply = control.validateReply(reply, validCodes1);

        // an empty array of files for 450/550
        String[] result = new String[0];

        // a normal reply ... extract the file list
        String replyCode = lastValidReply.getReplyCode();
        if (!replyCode.equals("450") && !replyCode.equals("550")) {
            // get an character input stream to read data from .
            LineNumberReader in = new LineNumberReader(new InputStreamReader(data.getInputStream()));

            // read a line at a time
            Vector lines = new Vector();
            String line = null;
            while ((line = in.readLine()) != null) {
                lines.add(line);
            }
            try {
                in.close();
                data.close();
            } catch (IOException ignore) {
            }

            // check the control response
            String[] validCodes2 = { "226", "250" };
            reply = control.readReply();
            lastValidReply = control.validateReply(reply, validCodes2);

            // empty array is default
            if (!lines.isEmpty())
                result = (String[]) lines.toArray(result);
        }
        return result;
    }

    /**
     * Gets the latest valid reply from the server
     * 
     * @return reply object encapsulating last valid server response
     */
    public FTPReply getLastValidReply() {
        return lastValidReply;
    }

    /**
     * Switch debug of responses on or off
     * 
     * @param on
     *            true if you wish to have responses to the log stream, false
     *            otherwise
     */
    public void debugResponses(boolean on) {
        control.debugResponses(on);
    }

    /**
     * Set the logging stream, replacing stdout
     * 
     * @param log
     *            the new logging stream
     */
    public void setLogStream(PrintWriter log) {
        control.setLogStream(log);
    }

    /**
     * Get the current transfer type
     * 
     * @return the current type of the transfer, i.e. BINARY or ASCII
     */
    public FTPTransferType getType() {
        return transferType;
    }

    /**
     * Set the transfer type
     * 
     * @param type
     *            the transfer type to set the server to
     */
    public void setType(FTPTransferType type) throws IOException, FTPException {

        // determine the character to send
        String typeStr = FTPTransferType.ASCII_CHAR;
        if (type.equals(FTPTransferType.BINARY))
            typeStr = FTPTransferType.BINARY_CHAR;

        // send the command
        String reply = control.sendCommand("TYPE " + typeStr);
        lastValidReply = control.validateReply(reply, "200");

        // record the type
        transferType = type;
    }

    /**
     * Delete the specified remote file
     * 
     * @param remoteFile
     *            name of remote file to delete
     */
    public void delete(String remoteFile) throws IOException, FTPException {

        String reply = control.sendCommand("DELE " + remoteFile);
        lastValidReply = control.validateReply(reply, "250");
    }

    /**
     * Rename a file or directory
     * 
     * @param from
     *            name of file or directory to rename
     * @param to
     *            intended name
     */
    public void rename(String from, String to) throws IOException, FTPException {

        String reply = control.sendCommand("RNFR " + from);
        lastValidReply = control.validateReply(reply, "350");

        reply = control.sendCommand("RNTO " + to);
        lastValidReply = control.validateReply(reply, "250");
    }

    /**
     * Delete the specified remote working directory
     * 
     * @param dir
     *            name of remote directory to delete
     */
    public void rmdir(String dir) throws IOException, FTPException {

        String reply = control.sendCommand("RMD " + dir);

        // some servers return 257, technically incorrect but
        // we cater for it ...
        String[] validCodes = { "250", "257" };
        lastValidReply = control.validateReply(reply, validCodes);
    }

    /**
     * Create the specified remote working directory
     * 
     * @param dir
     *            name of remote directory to create
     */
    public void mkdir(String dir) throws IOException, FTPException {

        String reply = control.sendCommand("MKD " + dir);
        lastValidReply = control.validateReply(reply, "257");
    }

    /**
     * Change the remote working directory to that supplied
     * 
     * @param dir
     *            name of remote directory to change to
     */
    public void chdir(String dir) throws IOException, FTPException {

        String reply = control.sendCommand("CWD " + dir);
        lastValidReply = control.validateReply(reply, "250");
    }

    /**
     * Get modification time for a remote file
     * 
     * @param remoteFile
     *            name of remote file
     * @return modification time of file as a date
     */
    public Date modtime(String remoteFile) throws IOException, FTPException {

        String reply = control.sendCommand("MDTM " + remoteFile);
        lastValidReply = control.validateReply(reply, "213");

        // parse the reply string ...
        Date ts = tsFormat.parse(lastValidReply.getReplyText(), new ParsePosition(0));
        return ts;
    }

    /**
     * Get the current remote working directory
     * 
     * @return the current working directory
     */
    public String pwd() throws IOException, FTPException {

        String reply = control.sendCommand("PWD");
        lastValidReply = control.validateReply(reply, "257");

        // get the reply text and extract the dir
        // listed in quotes, if we can find it. Otherwise
        // just return the whole reply string
        String text = lastValidReply.getReplyText();
        int start = text.indexOf('"');
        int end = text.lastIndexOf('"');
        if (start >= 0 && end > start)
            return text.substring(start + 1, end);
        else
            return text;
    }

    /**
     * Get the type of the OS at the server
     * 
     * @return the type of server OS
     */
    public String system() throws IOException, FTPException {

        String reply = control.sendCommand("SYST");
        lastValidReply = control.validateReply(reply, "215");
        return lastValidReply.getReplyText();
    }

    /**
     * Get the help text for the specified command
     * 
     * @param command
     *            name of the command to get help on
     * @return help text from the server for the supplied command
     */
    public String help(String command) throws IOException, FTPException {

        String reply = control.sendCommand("HELP " + command);
        String[] validCodes = { "211", "214" };
        lastValidReply = control.validateReply(reply, validCodes);
        return lastValidReply.getReplyText();
    }

    /**
     * Quit the FTP session
     * 
     */
    public void quit() throws IOException, FTPException {

        try {
            String reply = control.sendCommand("QUIT");
            String[] validCodes = { "221", "226" };
            lastValidReply = control.validateReply(reply, validCodes);
        } finally { // ensure we clean up the connection
            control.logout();
            control = null;
        }
    }

}
